package com.elitesland.system.service.impl;

import com.elitesland.core.base.PagingVO;
import com.elitesland.system.annotation.SysCodeProc;
import com.elitesland.system.convert.SysNumberRuleConvert;
import com.elitesland.system.entity.QSysNumberRuleDO;
import com.elitesland.system.param.SysNumberRuleQParam;
import com.elitesland.system.repo.SysNumberRuleRepo;
import com.elitesland.system.service.SysNextNumberService;
import com.elitesland.system.service.SysNumberRuleDtlService;
import com.elitesland.system.service.SysNumberRuleService;
import com.elitesland.system.vo.SysNumberRuleVO;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import lombok.val;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * <pre></pre>
 *
 * @author mir
 * @date 2020-10-11
 */
@Service
public class SysNumberRuleServiceImpl implements SysNumberRuleService {

    private SysNumberRuleRepo sysNumberRuleRepo;

    private SysNumberRuleDtlService sysNumberRuleDtlService;

    private SysNextNumberService sysNextNumberService;

    @Autowired
    public void setSysNumberRuleRepo(SysNumberRuleRepo sysNumberRuleRepo) {
        this.sysNumberRuleRepo = sysNumberRuleRepo;
    }

    @Autowired
    public void setSysNumberRuleDtlService(SysNumberRuleDtlService sysNumberRuleDtlService) {
        this.sysNumberRuleDtlService = sysNumberRuleDtlService;
    }

    @Autowired
    public void setSysNextNumberService(SysNextNumberService sysNextNumberService) {
        this.sysNextNumberService = sysNextNumberService;
    }

    @Override
    public String generateSampleCode(Long ruleId, List<String> runtimeValues) {
        val sampleCodeBuilder = new StringBuilder();

        val ruleDetails = sysNumberRuleDtlService.listByRuleId(ruleId);
        var ref = new Object() {
            int offset = 0;
        };
        ruleDetails.forEach(dtl -> {
            switch (dtl.getNumberType()) {
                case "FS":
                    sampleCodeBuilder.append(dtl.getNumberPattern());
                    break;
                case "DP":
                    sampleCodeBuilder.append(LocalDateTime.now().format(
                            DateTimeFormatter.ofPattern(dtl.getNumberPattern())
                    ));
                    break;
                case "NN":
                    sampleCodeBuilder.append(
                            StringUtils.leftPad("1", dtl.getNnLen(), "0")
                    );
                    break;
                default:
                    if (ref.offset < runtimeValues.size()) {
                        sampleCodeBuilder.append(runtimeValues.get(ref.offset));
                        ref.offset++;
                    }
                    break;
            }
        });
        return sampleCodeBuilder.toString();
    }

    @Override
    public String generateSampleCode(String ruleCode, List<String> runtimeValues) {
        return sysNumberRuleRepo.findByRuleCode(ruleCode)
                .map(rule -> generateSampleCode(rule.getId(), runtimeValues)).orElse("");
    }

    @Override
    public String generateCode(Long ruleId, List<String> runtimeValues) {
        val sampleCodeBuilder = new StringBuilder();

        val ruleDetails = sysNumberRuleDtlService.listByRuleId(ruleId);
        var ref = new Object() {
            int offset = 0;
        };
        ruleDetails.forEach(dtl -> {
            switch (dtl.getNumberType()) {
                case "FS":
                    sampleCodeBuilder.append(dtl.getNumberPattern());
                    break;
                case "DP":
                    sampleCodeBuilder.append(LocalDateTime.now().format(
                            DateTimeFormatter.ofPattern(dtl.getNumberPattern())
                    ));
                    break;
                case "NN":
                    val nextNumber = sysNextNumberService.getNextNumber(dtl.getNumberPattern());
                    sampleCodeBuilder.append(
                            StringUtils.leftPad(nextNumber.toString(), dtl.getNnLen(), "0")
                    );
                    break;
                default:
                    if (ref.offset < runtimeValues.size()) {
                        sampleCodeBuilder.append(runtimeValues.get(ref.offset));
                        ref.offset++;
                    }
                    break;
            }
        });
        return sampleCodeBuilder.toString();
    }

    @Override
    public String generateCode(String ruleCode, List<String> runtimeValues) {
        return sysNumberRuleRepo.findByRuleCode(ruleCode)
                .map(rule -> generateCode(rule.getId(), runtimeValues)).orElse("");
    }

    @Override
    @SysCodeProc
    public PagingVO<SysNumberRuleVO> search(SysNumberRuleQParam param) {
        val ruleTable = QSysNumberRuleDO.sysNumberRuleDO;
        Predicate predicate = ruleTable.isNotNull().or(ruleTable.isNull());

        predicate = StringUtils.isBlank(param.getRuleCode()) ? predicate : ExpressionUtils.and(predicate,
                ruleTable.ruleCode.like("%" + param.getRuleCode() + "%"));
        predicate = StringUtils.isBlank(param.getRuleName()) ? predicate : ExpressionUtils.and(predicate,
                ruleTable.ruleName.like("%" + param.getRuleName() + "%"));
        predicate = StringUtils.isBlank(param.getRuleClass()) ? predicate : ExpressionUtils.and(predicate,
                ruleTable.ruleClass.eq(param.getRuleClass()));

        val rules = sysNumberRuleRepo.findAll(predicate, param.getPageRequest());
        return PagingVO.<SysNumberRuleVO>builder()
                .total(rules.getTotalElements())
                .records(
                        rules.getContent().stream().map(SysNumberRuleConvert.INSTANCE::doToVO)
                                .collect(Collectors.toList())
                )
                .build();
    }

    @Override
    public Optional<SysNumberRuleVO> oneCombined(Long id) {
        return sysNumberRuleRepo.findById(id).map(rule -> {
            val ruleVO = SysNumberRuleConvert.INSTANCE.doToVO(rule);
            val details = sysNumberRuleDtlService.listByRuleId(rule.getId());
            ruleVO.setRuleDetails(details);

            return Optional.of(ruleVO);
        }).orElse(Optional.empty());
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public Long create(SysNumberRuleVO rule) {
        val ruleDO = SysNumberRuleConvert.INSTANCE.voToDO(rule);
        val newRule = sysNumberRuleRepo.save(ruleDO);
        return newRule.getId();
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void update(SysNumberRuleVO rule) {
        sysNumberRuleRepo.findById(rule.getId()).ifPresent(oldRule -> {
            oldRule
                    .setRuleCode(rule.getRuleCode())
                    .setRuleName(rule.getRuleName())
                    .setRuleClass(rule.getRuleClass());
            sysNumberRuleRepo.save(oldRule);
        });
    }

    @Override
    @Transactional(rollbackOn = {Exception.class})
    public void removeById(Long id) {
        sysNumberRuleRepo.deleteById(id);
    }
}
